/** * Copyright (c) 2014-2017 by the respective copyright holders. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html */ package org.eclipse.smarthome.io.javasound.internal; import java.io.IOException; import javax.sound.sampled.AudioFormat; import javax.sound.sampled.AudioSystem; import javax.sound.sampled.DataLine; import javax.sound.sampled.Line; import javax.sound.sampled.Line.Info; import javax.sound.sampled.Mixer; import javax.sound.sampled.SourceDataLine; import org.eclipse.smarthome.core.audio.AudioStream; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * This is a class that plays an AudioStream through the Java sound API * * @author Kelly Davis - Initial contribution and API * @author Kai Kreuzer - Refactored to use AudioStream and logging * */ public class AudioPlayer extends Thread { private final Logger logger = LoggerFactory.getLogger(AudioPlayer.class); /** * The AudioStream to play */ private final AudioStream audioStream; /** * Constructs an AudioPlayer to play the passed AudioSource * * @param audioSource The AudioSource to play */ public AudioPlayer(AudioStream audioStream) { this.audioStream = audioStream; } /** * This method plays the contained AudioSource */ @Override public void run() { SourceDataLine line; AudioFormat audioFormat = convertAudioFormat(this.audioStream.getFormat()); if (audioFormat == null) { logger.warn("Audio format is unsupported or does not have enough details in order to be played"); return; } DataLine.Info info = new DataLine.Info(SourceDataLine.class, audioFormat); try { line = (SourceDataLine) AudioSystem.getLine(info); line.open(audioFormat); } catch (Exception e) { logger.warn("No line found: {}", e.getMessage()); logger.info("Available lines are:"); Mixer.Info[] mixerInfo = AudioSystem.getMixerInfo(); // get available mixers Mixer mixer = null; for (int cnt = 0; cnt < mixerInfo.length; cnt++) { mixer = AudioSystem.getMixer(mixerInfo[cnt]); Line.Info[] lineInfos = mixer.getSourceLineInfo(); for (Info lineInfo : lineInfos) { logger.info(lineInfo.toString()); } } return; } line.start(); int nRead = 0; byte[] abData = new byte[65532]; // needs to be a multiple of 4 and 6, to support both 16 and 24 bit stereo try { while (-1 != nRead) { nRead = audioStream.read(abData, 0, abData.length); if (nRead >= 0) { line.write(abData, 0, nRead); } } } catch (IOException e) { logger.error("Error while playing audio: {}", e.getMessage()); return; } finally { line.drain(); line.close(); try { audioStream.close(); } catch (IOException e) { } } } /** * Converts a org.eclipse.smarthome.core.audio.AudioFormat * to a javax.sound.sampled.AudioFormat * * @param audioFormat The AudioFormat to convert * @return The corresponding AudioFormat */ @SuppressWarnings("null") protected AudioFormat convertAudioFormat(org.eclipse.smarthome.core.audio.AudioFormat audioFormat) { AudioFormat.Encoding encoding = new AudioFormat.Encoding(audioFormat.getCodec()); if (audioFormat.getCodec().equals(org.eclipse.smarthome.core.audio.AudioFormat.CODEC_PCM_SIGNED)) { encoding = AudioFormat.Encoding.PCM_SIGNED; } else if (audioFormat.getCodec().equals(org.eclipse.smarthome.core.audio.AudioFormat.CODEC_PCM_ULAW)) { encoding = AudioFormat.Encoding.ULAW; } else if (audioFormat.getCodec().equals(org.eclipse.smarthome.core.audio.AudioFormat.CODEC_PCM_ALAW)) { encoding = AudioFormat.Encoding.ALAW; } Float sampleRate = audioFormat.getFrequency() != null ? audioFormat.getFrequency().floatValue() : null; Integer sampleSizeInBits = audioFormat.getBitDepth() != null ? audioFormat.getBitDepth().intValue() : null; Integer channels = 1; // TODO: Is this always true? Integer frameSize = audioFormat.getBitDepth() != null ? audioFormat.getBitDepth().intValue() / 8 : null; // frameSize Float frameRate = (sampleRate != null && frameSize != null) ? (sampleRate / frameSize) : null; Boolean bigEndian = audioFormat.isBigEndian() != null ? audioFormat.isBigEndian().booleanValue() : null; try { return new AudioFormat(encoding, sampleRate, sampleSizeInBits, channels, frameSize, frameRate, bigEndian); } catch (NullPointerException e) { return null; } } }